#include "AttriBinding.hpp"

namespace Data {

void VertexState::CreateBindingPoint() {
  if (_enabled_points == 0) {
    for (int i = 0; i < VertexAttributes::Count; i++) {
      _bindings.WriteAttributeLoc(i, i);
    }
    _points.at(_enabled_points++) =
        std::make_optional<BindingPoint>(true, _enabled_points);
  } else {
    _points.at(_enabled_points++) =
        std::make_optional<BindingPoint>(false, _enabled_points);
  }
}

void VertexState::AddAttributeBinding(const AttributeFormat &format) {
  AddAttributeBinding(_bindings.FindUnusedAttribute(), format);
}

void VertexState::AddAttributeBinding(GLuint attributeIndex,
                                      const AttributeFormat &format) {
  AddAttributeBinding(attributeIndex, 0, _bindings.GetCurrentMaxLoc(), format);
}

void VertexState::AddAttributeBinding(GLuint attributeIndex, GLuint bindingIndex,
                                      GLuint loc, const AttributeFormat &format) {
  _points.at(bindingIndex)->AddAttributeFormat(attributeIndex, loc, format);
  _bindings.WriteAttributeLoc(attributeIndex, loc);
}

void VertexState::RemoveAttributeBinding(GLuint attributeIndex, GLuint bindingIndex) {
  assert(attributeIndex < MAX_VERTEX_ATTRIBUTE_NUM);
  _points.at(bindingIndex)->RemoveAttributeFormat(attributeIndex);
  _bindings.DisableAttribute(attributeIndex);
}

void VertexState::RemoveBindingPoint(size_t i) {
  _enabled_points--;
  _points.at(i)->Clear();
  _points.at(i).reset();
}

void VertexState::BindingPoint::Clear() {
  for (auto &val : _formats) {
    val.Clear();
  }
}

std::vector<GLuint> VertexState::GetEnabledAttributeIndex(GLuint bindingIndex) {
  return _points.at(bindingIndex)->GetEnabledAttribute();
}

void VertexState::init() {
  glCreateVertexArrays(1, &_id);
  glBindVertexArray(_id);
}

void VertexState::Update(GLuint program) {
  GLuint i = 0;
  auto size = _bindings.GetAttributeSize();
  for (auto &point : _points) {
    // skip un enabled bind point
    if (!point.has_value())
      continue;
    auto attributeIndices = point->GetEnabledAttribute();
    for (const auto &val : attributeIndices) {
      if (i++ > size)
        return;
      glEnableVertexArrayAttrib(_id, val);
      glVertexArrayAttribFormat(_id, val, (*point)[val].component,
                                GetGLType((*point)[val].dataType), GL_FALSE,
                                (*point)[val].relativeOffset);
      glVertexArrayAttribBinding(_id, val, point->GetID());
      if (program != 0) {
        glBindAttribLocation(program, val, (*point)[val].name.c_str());
      }
    }
  }
}

void VertexState::BindingPoint::init() {
  _attributes.fill(MAX_VERTEX_ATTRIBUTE_NUM);
  if (_vertexAttrib_enabled) {
    for (int i = 0; i < VertexAttributes::Count; i++) {
      auto format =
          AttributeFormat(GetVertexAttributeName(static_cast<VertexAttributes>(i)),
                          AttriDataType::FLOAT32, 3, VERTEX_FORMAT_OFFSET[i]);
      if (i == VertexAttributes::TexCoordinate) {
        format.component = 2;
      }
      _formats[i] = format;
      _attributes[i] = i;
    }
  }
} // namespace Data

void VertexState::BindingPoint::RemoveAttributeFormat(GLuint attributeIndex) {
  assert(attributeIndex < MAX_VERTEX_ATTRIBUTE_NUM);
  _attributes[attributeIndex] = MAX_VERTEX_ATTRIBUTE_NUM;
  _formats[attributeIndex].Clear();
}

GLuint VertexState::BindingPoint::FindUnusedAttribute() {
  for (int i = 0; i < MAX_VERTEX_ATTRIBUTE_NUM; i++) {
    if (_formats[i].name.empty()) {
      return i;
    }
  }
  return 0;
}

std::vector<GLuint> VertexState::BindingPoint::GetEnabledAttribute() {
  std::vector<GLuint> indices;
  for (int i = 0; i < _attributes.size(); i++) {
    if (_attributes.at(i) != MAX_VERTEX_ATTRIBUTE_NUM) {
      indices.push_back(_attributes.at(i));
    }
  }
  return indices;
}

} // namespace Data